/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */


#include <tools/list.h>
#include <tools/macros.h>
#include <core/endpoint.h>
#include <core/fault.h>
#include <core/schedule.h>
#include <core/task.h>
#include <uapi/errors.h>
#include <uapi/ipcbuffer.h>

/* PRQA S 0570, 1881, 4423, 4424, 4521 ++ # enum */
/* PRQA S 2890 ++ # negative convert, task_set_state */
/* PRQA S 3432 ++ # macro not parenthesized */
/* PRQA S 1532 ++ # one translation unit */

void endpoint_init(endpoint_t *ep)
{
    list_init(&ep->waiters);
    ep->state  = EP_IDLE;
    ep->server = NULL;
    object_init(&ep->obj);
}

static void endpoint_enque(endpoint_t *ep, tcb_t *task)
{
    tcb_t  *cur;
    list_t *pos = &ep->waiters;

    schedule_detach(task);

    if (0u == (task_get_flags(task) & TASK_FBIT_REPORT)) {
        task_set_state(task, TASK_BLOCKED);
    }

    list_foreach(cur, &ep->waiters, tcb_t, runlist)
    {
        if (cur->priority > task->priority) {
            pos = cur->runlist.prev;
            break;
        }
    }
    list_insert(pos, &task->runlist);
}

static inline void endpoint_deque(tcb_t *task)
{
    list_remove(&task->runlist);

    if (0u == (task_get_flags(task) & TASK_FBIT_REPORT)) {
        task_set_state(task, TASK_READY);
    }
}

static inline void try_promote_priority(tcb_t *dst, tcb_t *src)
{
    if (task_get_priority(dst) > task_get_priority(src)) {
        if (!list_is_empty(&dst->runlist)) {
            schedule_detach(dst);
            task_set_priority(dst, task_get_priority(src));
            schedule_attach(dst);
        } else {
            task_set_priority(dst, task_get_priority(src));
        }
    }
}

static inline void recovery_priority(tcb_t *tsk)
{
    task_set_priority(tsk, task_get_maxpriority(tsk));
}

void endpoint_send(endpoint_t *ep, unsigned int tag, unsigned long m0, unsigned long m1, unsigned long m2,
                   unsigned long m3, unsigned long m4, unsigned long m5, void *_sender)
{
    tcb_t *receiver;
    tcb_t *sender = _sender;
    switch (ep->state) {
        case EP_SEND:
        case EP_IDLE:
            if (ep->server != NULL) {
                try_promote_priority(ep->server, sender);
            }

            if (msgtag_get_extra(tag) != 0U) {
                task_set_retcode(sender, ERR_TO_RETCODE(-EAGAIN));
            } else {
                ipcbuffer_save(task_get_ipcbuffer(sender), tag, m0, m1, m2, m3, m4, m5);
                tcb_get(sender);
                endpoint_enque(ep, sender);
                ep->state = EP_SEND;
            }
            return;

        case EP_RECV:
            receiver = container_of(ep->waiters.next, tcb_t, runlist);
            endpoint_deque(receiver);
            if (list_is_empty(&ep->waiters)) {
                ep->state = EP_IDLE;
            }
            break;

        default:
            task_set_retcode(sender, ERR_TO_RETCODE(-EINVALID));
            return;
    }

    ipcbuffer_regs_transfer(receiver, tag, m0, m1, m2, m3, m4, m5);
    ipcbuffer_partial_transfer(receiver, sender, tag);
    task_set_retcode(receiver, ESUCCESS);

    if (0U != (task_get_flags(sender) & TASK_FBIT_CALL)) {
        endpoint_get(ep);
        tcb_get(sender);
        sender->calledep = ep;
        list_insert(&receiver->callers, &sender->callernode);
        task_set_state(sender, TASK_BLOCKED);
        schedule_detach(sender);
        task_clear_flags(sender, TASK_FBIT_CALL);

        try_promote_priority(receiver, sender);
    } else {
        task_set_retcode(sender, ESUCCESS);
    }

    schedule_attach(receiver);
    (void)tcb_put(receiver);
}

void endpoint_receive(endpoint_t *ep, unsigned int tag, tcb_t *receiver)
{
    tcb_t *sender;

    assert(tcb_is_valid(receiver));
    switch (ep->state) {
        case EP_IDLE:
        case EP_RECV:
            if (msgtag_get_extra(tag) != 0U) {
                task_set_retcode(receiver, ERR_TO_RETCODE(-EAGAIN));
            } else {
                tcb_get(receiver);
                endpoint_enque(ep, receiver);
                ep->state = EP_RECV;
            }
            break;

        case EP_SEND:
            sender = container_of(ep->waiters.next, tcb_t, runlist);
            endpoint_deque(sender);

            ipcbuffer_full_transfer(receiver, sender);

            if (0U != (task_get_flags(sender) & TASK_FBIT_CALL)) {
                endpoint_get(ep);
                sender->calledep = ep;
                list_insert(&receiver->callers, &sender->callernode);
                task_set_state(sender, TASK_BLOCKED);
                task_clear_flags(sender, TASK_FBIT_CALL);

                try_promote_priority(receiver, sender);
            } else {
                task_set_retcode(sender, ESUCCESS);
                schedule_attach(sender);
                (void)tcb_put(sender);
            }

            task_set_retcode(receiver, ESUCCESS);

            if (list_is_empty(&ep->waiters)) {
                ep->state = EP_IDLE;
            }
            break;

        default:
            task_set_retcode(receiver, ERR_TO_RETCODE(-EINVALID));
            break;
    }
}

void endpoint_call(endpoint_t *ep, unsigned int tag, unsigned long m0, unsigned long m1, unsigned long m2,
                   unsigned long m3, unsigned long m4, unsigned long m5, void *_caller)
{
    tcb_t *caller = _caller;
    task_set_flags(caller, TASK_FBIT_CALL);
    endpoint_send(ep, tag, m0, m1, m2, m3, m4, m5, caller);
}

int endpoint_reply(endpoint_t *ep, unsigned int tag, unsigned int m0, unsigned int m1, unsigned long m2,
                   unsigned long m3, unsigned long m4, unsigned long m5, void *_replier)
{
    tcb_t *replier = _replier;
    tcb_t *caller  = container_of(replier->callers.next, tcb_t, callernode);

    assert(tcb_is_valid(replier));

    if (list_is_empty(&replier->callers) || (caller->calledep != ep)) {
        task_set_retcode(replier, ERR_TO_RETCODE(-EGENERAL));
        return -EGENERAL;
    }

    list_remove(&caller->callernode);
    ipcbuffer_regs_transfer(caller, tag, m0, m1, m2, m3, m4, m5);
    ipcbuffer_partial_transfer(caller, replier, tag);

    recovery_priority(replier);

    task_set_retcode(caller, ESUCCESS);
    if (0U != (task_get_flags(caller) & TASK_FBIT_REPORT)) {
        fault_callback(caller, m0);
    } else {
        task_set_state(caller, TASK_READY);
        schedule_attach(caller);
    }
    (void)tcb_put(caller);

    task_set_retcode(replier, ESUCCESS);
    endpoint_put(ep);
    return ESUCCESS;
}

void endpoint_reply_wait(endpoint_t *ep, unsigned int tag, unsigned int m0, unsigned int m1, unsigned long m2,
                         unsigned long m3, unsigned long m4, unsigned long m5, void *replier)
{
    if (endpoint_reply(ep, tag, m0, m1, m2, m3, m4, m5, replier) == ESUCCESS) {
        endpoint_receive(ep, tag, replier);
    }
}

void endpoint_cancel(endpoint_t *ep)
{
    tcb_t *waiter;

    while (!list_is_empty(&ep->waiters)) {
        waiter = container_of(ep->waiters.next, tcb_t, runlist);
        endpoint_deque(waiter);
        task_clear_flags(waiter, TASK_FBIT_CALL);
        task_set_retcode(waiter, ERR_TO_RETCODE(-ECANCELED));
        schedule_attach(waiter);
    }
    ep->state = EP_DIED;
}

void endpoint_clean_callers(tcb_t *tsk)
{
    tcb_t *cur;

    assert(tcb_is_valid(tsk));

    while (!list_is_empty(&tsk->callers)) {
        cur = container_of(tsk->callers.next, tcb_t, callernode);
        list_remove(&cur->callernode);

        task_set_state(cur, TASK_READY);
        task_set_retcode(cur, ERR_TO_RETCODE(-ECANCELED));
        schedule_attach(cur);
        (void)tcb_put(cur);
        endpoint_put(cur->calledep);
    }
}

void endpoint_bind(endpoint_t *ep, tcb_t *tsk)
{
    tcb_t *sender = container_of(ep->waiters.next, tcb_t, runlist);

    tcb_get(tsk);
    ep->server = tsk;

    if (ep->state == EP_SEND) {
        try_promote_priority(tsk, sender);
    }
    task_set_retcode(tsk, ESUCCESS);
}

void endpoint_unbind(endpoint_t *ep, tcb_t *tsk)
{
    if (ep->server == tsk) {
        ep->server = NULL;

        recovery_priority(tsk);
        task_set_retcode(tsk, ESUCCESS);
        (void)tcb_put(tsk);
    } else {
        task_set_retcode(tsk, ERR_TO_RETCODE(-EPERMIT));
    }
}
